package com.zyw.mvc.exception.handler;

import com.alibaba.fastjson.JSON;
import com.zyw.mvc.exception.utils.ExceptionUtils;
import com.zyw.mvc.support.WrappedResult;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * rest 请求异常处理解析器
 * Created by vill on 2014/7/1.
 */
public class RestExceptionHandlerResolver extends ExceptionHandlerExceptionResolver {

    /**
     * 是否返回堆栈信息
     */
    private boolean fullStack;

    private List<RestExceptionHandler> restExceptionHandlers;

    private List<RestExceptionHandler> restCustomExceptionHandlers;

    /**
     * 默认异常页面视图
     */
    private String defaultView;

    @Override
    protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) {
        if (handlerMethod == null) {
            return null;
        }

        Method method = handlerMethod.getMethod();

        if (method == null) {
            return null;
        }
        //如果定义了ExceptionHandler则返回相应的Map中的数据
        // 通过ExceptionHandler注解添加返回异常处理信息
        ModelAndView returnValue = super.doResolveHandlerMethodException(request, response, handlerMethod, exception);
        ResponseBody responseBody = AnnotationUtils.findAnnotation(method, ResponseBody.class);
        // 添加 responsebody 异常处理
        if (responseBody != null) {
            try {
                ResponseStatus responseStatus = AnnotationUtils.findAnnotation(method, ResponseStatus.class);
                if (responseStatus != null) {// 通过responseStatus 注解添加返回异常信息
                    HttpStatus httpStatus = responseStatus.value();
                    String reason = responseStatus.reason();
                    if (!StringUtils.hasText(reason)) {
                        response.setStatus(httpStatus.value());
                    } else {
                        try {
                            response.sendError(httpStatus.value(), reason);
                        } catch (IOException e) {
                            logger.warn("send error message", e);
                        }
                    }
                }
                // 如果没有ExceptionHandler注解那么returnValue就为空
                if (null == returnValue) {
                    return handleResponseError(handleFailedResult(request, exception), request, response);
                }
                return handleResponseBody(returnValue, request, response);
            } catch (Exception e) {
                logger.warn("resolve handler method exception error", e);
                return null;
            }
        } else {// 未加responsebody注解 异常处理
            if (null == returnValue) {
                try {
                    returnValue = handleResponseError(handleFailedResult(request, exception), request, response);
                } catch (ServletException | IOException e) {
                    logger.warn("resolve handler method exception error", e);
                    return null;
                }
            }
            return returnValue;
        }

    }

    private ModelAndView handleResponseBody(ModelAndView returnValue, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Map value = returnValue.getModelMap();
        HttpInputMessage inputMessage = new ServletServerHttpRequest(request);
        List<MediaType> acceptedMediaTypes = inputMessage.getHeaders().getAccept();
        if (acceptedMediaTypes.isEmpty()) {
            acceptedMediaTypes = Collections.singletonList(MediaType.ALL);
        }
        MediaType.sortByQualityValue(acceptedMediaTypes);
        HttpOutputMessage outputMessage = new ServletServerHttpResponse(response);
        Class<?> returnValueType = value.getClass();
        List<HttpMessageConverter<?>> messageConverters = super.getMessageConverters();
        if (messageConverters != null) {
            for (MediaType acceptedMediaType : acceptedMediaTypes) {
                for (HttpMessageConverter messageConverter : messageConverters) {
                    if (messageConverter.canWrite(returnValueType, acceptedMediaType)) {
                        messageConverter.write(value, acceptedMediaType, outputMessage);
                        return new ModelAndView();
                    }
                }
            }
        }
        if (logger.isWarnEnabled()) {
            logger.warn("Could not find HttpMessageConverter that supports return type [" + returnValueType + "] and " + acceptedMediaTypes);
        }
        return null;
    }

    /**
     * 处理 默认 rest请求异常
     *
     * @param returnValue
     * @param request
     * @param response
     * @return
     * @throws ServletException
     * @throws IOException
     */
    private ModelAndView handleResponseError(Object returnValue, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpInputMessage inputMessage = new ServletServerHttpRequest(request);
        List<MediaType> acceptedMediaTypes = inputMessage.getHeaders().getAccept();
        if (acceptedMediaTypes.isEmpty()) {
            acceptedMediaTypes = Collections.singletonList(MediaType.ALL);
        }
        MediaType.sortByQualityValue(acceptedMediaTypes);
        HttpOutputMessage outputMessage = new ServletServerHttpResponse(response);
        Class<?> returnValueType = returnValue.getClass();
        List<HttpMessageConverter<?>> messageConverters = super.getMessageConverters();
        if (messageConverters != null) {
            for (MediaType acceptedMediaType : acceptedMediaTypes) {
                for (HttpMessageConverter messageConverter : messageConverters) {
                    if (messageConverter.canWrite(returnValueType, acceptedMediaType)) {
                        messageConverter.write(returnValue, acceptedMediaType, outputMessage);
                        return new ModelAndView();
                    }
                }
            }
        }
        if (logger.isWarnEnabled()) {
            logger.warn("Could not find HttpMessageConverter that supports return type [" + returnValueType + "] and " + acceptedMediaTypes);
        }
        return null;
    }

    public WrappedResult handleFailedResult(HttpServletRequest request, Exception exception) {

        WrappedResult wrappedResult;

        for (RestExceptionHandler restExceptionHandler : restExceptionHandlers) {
            if (restExceptionHandler.support(exception)) {
                wrappedResult = restExceptionHandler.handleInvoke(exception);
                request.setAttribute("success", false);
                request.setAttribute("result", JSON.toJSONString(wrappedResult));
                return wrappedResult;
            }
        }

        String exMessage = exception.getMessage();
        logger.error(ExceptionUtils.getFullStackTrace(exception));

        if (exMessage == null)
            return WrappedResult.handleExceptionWrappedResult("操作异常！");
        if (isFullStack()) {
            return WrappedResult.handleExceptionWrappedResult(ExceptionUtils.getFullStackTrace(exception));
        } else {
            return WrappedResult.handleExceptionWrappedResult(exMessage);
        }
    }

    public boolean isFullStack() {
        return fullStack;
    }

    public void setFullStack(boolean fullStack) {
        this.fullStack = fullStack;
    }

    public String getDefaultView() {
        return defaultView;
    }

    public void setDefaultView(String defaultView) {
        this.defaultView = defaultView;
    }

    public List<RestExceptionHandler> getDefaultRestExceptionHandlers() {
        if (restExceptionHandlers == null) {
            restExceptionHandlers = new LinkedList<RestExceptionHandler>();
            restExceptionHandlers.add(new InvalidParameterExceptionHandler(this.fullStack));
            restExceptionHandlers.add(new RestDefaultExceptionHandler(this.fullStack));
        }
        return restExceptionHandlers;
    }

    public void setRestCustomExceptionHandlers(List<RestExceptionHandler> restCustomExceptionHandlers) {
        this.restCustomExceptionHandlers = restCustomExceptionHandlers;
    }

    @Override
    public void afterPropertiesSet() {
        super.afterPropertiesSet();
        if (restExceptionHandlers == null) {
            restExceptionHandlers = getDefaultRestExceptionHandlers();
        }

        if (restCustomExceptionHandlers != null) {
            restExceptionHandlers.addAll(restCustomExceptionHandlers);
        }
    }
}
